package export.ldif;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLSocketFactory;

import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.RootDSE;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.util.ssl.SSLUtil;
import com.unboundid.util.ssl.TrustAllTrustManager;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;


public class LDAPUtility 
{
	private String _delimiter = "|";
	
	private Path _fileIn = Paths.get("");
	
	private Multimap<Integer,String> _fileLayout = ArrayListMultimap.create();
	
	private LDAPCredentials _ldapCredentials = new LDAPCredentials();
	
	
	private LDAPUtility()
	{
		super();
		
	}//constructor

	
	public static LDAPUtility factory()
	{
		return new LDAPUtility();
		
	}//method

	
	public LDAPUtility withDelimiter( final String delimIn )
	{
		_delimiter = delimIn;
		
		return this;
		
	}//method

	
	public LDAPUtility withFile( final String filePathIn )
	{
		checkNotNull( filePathIn, "null file path" );

		checkArgument( Files.exists( Paths.get( filePathIn ) ), 
				"file not exist: " + filePathIn );

		checkArgument( Files.isRegularFile( Paths.get( filePathIn ) ), 
				"not a file: " + filePathIn );
		
		checkArgument( Paths.get( filePathIn ).toFile().length() > 0, 
				"file empty: " + filePathIn );

		_fileIn = Paths.get( filePathIn );
		
		return this;
		
	}//method


	/**
	 * Set File Layout.<p>
	 * 
	 * Multimap describing file layout:<br>
	 * 
	 * <ol>
	 * <li>Integer = column number
	 * 
	 * <li>String = ldap file name; example: "cn"
	 * 
	 * <li>String = "match" or "update"; use as a key or update ldap with the 
	 * 	data
	 * </ol>
	 * 
	 * @param fileLayout - Multimap containing file layout
	 * 
	 * @return - this
	 */
	
	public LDAPUtility withFileLayout( final Multimap<Integer,String> 
		fileLayout )
	{
		checkNotNull( fileLayout, "null file layout" );

		_fileLayout = fileLayout;
		
		return this;
		
	}//method

	
	public LDAPUtility withLDAPHost( final String ldapHost )
	{
		checkNotNull( ldapHost, "null ldap host" );

		_ldapCredentials.withHost( ldapHost );
		
		return this;
		
	}//method


	/**
	 * Set ldap admin dn.<p>
	 * 
	 * Example: cn=admin,dc=mydomain,dc=com<p>
	 * 
	 * @param ldapAdmin - String admin dn
	 * 
	 * @return - this
	 */
	
	public LDAPUtility withLDAPAdmin( final String ldapAdmin )
	{
		checkNotNull( ldapAdmin, "null ldap admin dn" );

		_ldapCredentials.withAdmin( ldapAdmin );
		
		return this;
		
	}//method

	
	public LDAPUtility withLDAPPassword( final String ldapPW )
	{
		checkNotNull( ldapPW, "null ldap password" );

		_ldapCredentials.withPassword( ldapPW );
		
		return this;
		
	}//method

	
	/**
	 * Connect to LDAP as test.<p>
	 * 
	 * Predicate result of LDAP Host, login dn and other known valid 
	 * 	literals, duplicated, stand-alone logic.<p>
	 * 
	 * @return Boolean - T if connection was established without error
	 * 
	 */
	
	public Boolean ldapOK()
	{
		Optional<LDAPConnection> ldapCon = Optional.absent();
		
		Boolean result = false;
		
		try
		{
			LDAPConnectionOptions ldapConOpt = new LDAPConnectionOptions();

			ldapConOpt.setBindWithDNRequiresPassword( true );
			ldapConOpt.setConnectTimeoutMillis( 9000 );
			ldapConOpt.setCaptureConnectStackTrace( true );		


			SSLUtil sslUtl = new SSLUtil( new TrustAllTrustManager() );
			SSLSocketFactory sslSkFactory = sslUtl.createSSLSocketFactory();

			ldapCon = Optional.of( new LDAPConnection( sslSkFactory, 
					ldapConOpt) );

			ldapCon.get().connect( _ldapCredentials.host(), 636 );

			//RootDSE rootDSE = ldapCon.getRootDSE();

			ldapCon.get().bind( _ldapCredentials.admin() , 
					_ldapCredentials.password() );

			result = true;
			
		}
		catch( final GeneralSecurityException | LDAPException err )
		{
			result = false;
			
			System.err.println( err + Throwables.getStackTraceAsString(err) );
			
		}
		finally
		{
			ldapCon.get().close();
			
		}//try
		
		return result;

	}//method
	
	
	
	
	
	
	/**
	 * Perform ldap update for one or more add, revise, or remove operations.
	 * <p>
	 * 
	 * @return  this
	 * 
	 * @throws LDAPException - general unboundid API error for any ldap error
	 * 
	 * @throws GeneralSecurityException - Java SSL socket factory security 
	 * 	manager error
	 */
	
	public LDAPUtility update() throws LDAPException, 
									   GeneralSecurityException
	{	
		LDAPConnectionOptions ldapConOpt = new LDAPConnectionOptions();

		ldapConOpt.setBindWithDNRequiresPassword( true );
		ldapConOpt.setConnectTimeoutMillis( 9000 );
		ldapConOpt.setCaptureConnectStackTrace( true );		


		SSLUtil sslUtl = new SSLUtil( new TrustAllTrustManager() );
		SSLSocketFactory sslSkFactory = sslUtl.createSSLSocketFactory();

		LDAPConnection ldapCon = new LDAPConnection( sslSkFactory, 
				ldapConOpt);

		ldapCon.connect( _ldapCredentials.host(), 636 );

		ldapCon.bind( _ldapCredentials.admin() , 
					  _ldapCredentials.password() );
		
		
		
		ldapCon.close();	

		return this;
		
	}//method

	
	class LDAPCredentials
	{
		private String _ldapHost = "";

		private String _ldapAdmin = "";

		private String _ldapPassword = "";

		
		LDAPCredentials() {}//constructor
		
		
		String host(){ return _ldapHost; }
		
		String admin(){ return _ldapAdmin; }
		
		String password(){ return _ldapPassword; }
		
		
		LDAPCredentials withHost( final String host )
		{
			checkNotNull( host, "ldap hostname null" );
			
			_ldapHost = host;
			
			return this;
			
		}//method

		
		LDAPCredentials withAdmin( final String admin )
		{
			checkNotNull( admin, "ldap admin dn null" );
			
			_ldapAdmin = admin;
			
			return this;
			
		}//method

		
		LDAPCredentials withPassword( final String password )
		{
			checkNotNull( password, "ldap password dn null" );
			
			_ldapPassword = password;
			
			return this;
			
		}//method
		
	}//inner class

	
}//class
